##################################################################
# File : /lua/ui/uiutil.lua
# Description : Various utility functions to make UI scripts easier and more consistent
# Author : GPG / Neruz
##################################################################


local LazyVar = import('/lua/lazyvar.lua')
local LayoutHelpers = import('/lua/maui/layouthelpers.lua')
local EffectHelpers = import('/lua/maui/effecthelpers.lua')
local Group = import('/lua/maui/group.lua').Group
local Text = import('/lua/maui/text.lua').Text
local MultiLineText = import('/lua/maui/multilinetext.lua').MultiLineText
local Button = import('/lua/maui/button.lua').Button
local Edit = import('/lua/maui/edit.lua').Edit
local Checkbox = import('/lua/maui/Checkbox.lua').Checkbox
local Scrollbar = import('/lua/maui/scrollbar.lua').Scrollbar
local Bitmap = import('/lua/maui/bitmap.lua').Bitmap
local Cursor = import('/lua/maui/cursor.lua').Cursor
local Prefs = import('/lua/user/prefs.lua')
local Border = import('/lua/maui/border.lua').Border
local ItemList = import('/lua/maui/itemlist.lua').ItemList
local Layouts = import('/lua/user/skins/layouts.lua')
local Movie = import('/lua/maui/movie.lua').Movie
local LinkText = import('/lua/maui/linktext.lua').LinkText
local CryptEdit = import('/lua/maui/cryptedit.lua').CryptEdit

# Handy global variables to assist skinning
buttonFont = import('/lua/lazyvar.lua').Create()            # default font used for button faces
buttonFontColor = import('/lua/lazyvar.lua').Create()            # default font used for button faces
buttonFontOverColor = import('/lua/lazyvar.lua').Create()            # default font used for button faces
buttonFontDownColor = import('/lua/lazyvar.lua').Create()            # default font used for button faces
buttonFontSelectedColor = import('/lua/lazyvar.lua').Create()            # default font used for button faces
dialogButtonFont = import('/lua/lazyvar.lua').Create()      # default font used for dialog button faces
bodyFont = import('/lua/lazyvar.lua').Create()              # font used for all other text
fixedFont = import('/lua/lazyvar.lua').Create()             # font used for fixed width characters
titleFont = import('/lua/lazyvar.lua').Create()             # font used for titles and labels
flashFont = import('/lua/lazyvar.lua').Create()             # font used for text flash
titleFontColor = import('/lua/lazyvar.lua').Create()             # common font color
fontColor = import('/lua/lazyvar.lua').Create()             # common font color
specialFontColor = import('/lua/lazyvar.lua').Create()             # common font color
fontOverColor = import('/lua/lazyvar.lua').Create()             # common font color
fontDownColor = import('/lua/lazyvar.lua').Create()             # common font color
bodyColor = import('/lua/lazyvar.lua').Create()             # common color for dialog body text
dialogCaptionColor = import('/lua/lazyvar.lua').Create()    # common color for dialog titles
dialogColumnColor = import('/lua/lazyvar.lua').Create()     # common color for column headers in a dialog
dialogButtonColor = import('/lua/lazyvar.lua').Create()     # common color for buttons in a dialog
highlightColor = import('/lua/lazyvar.lua').Create()        # text highlight color
disabledColor = import('/lua/lazyvar.lua').Create()         # text disabled color
panelColor = import('/lua/lazyvar.lua').Create()            # default color when drawing a panel
transparentPanelColor = import('/lua/lazyvar.lua').Create() # default color when drawing a transparent panel
consoleBGColor = import('/lua/lazyvar.lua').Create()        # console background color
consoleFGColor = import('/lua/lazyvar.lua').Create()        # console foreground color (text)
consoleTextBGColor = import('/lua/lazyvar.lua').Create()    # console text background color
layouts = nil                                                       # table of layouts supported by this skin, not a lazy var as we don't need updates

# other handy variables!
consoleDepth = false  # in order to get the console to always be on top, assign this number and never go over

networkBool = import('/lua/lazyvar.lua').Create()    # boolean whether the game is local or networked

# These values MUST NOT CHANGE! They syncronize with values in UIManager.h and are used to
# specify a render pass
# render before the world is rendered
UIRP_UnderWorld     = 1
# reserved for world views
UIRP_World          = 2
# render with glow (note, won't render when world isn't visible)
UIRP_Glow           = 4
# render without glow
UIRP_PostGlow       = 8

# useful key codes
VK_BACKSPACE = 8
VK_TAB = 9
VK_ENTER = 13
VK_ESCAPE = 27
VK_SPACE = 32
VK_PRIOR = 33
VK_NEXT = 34
VK_UP = 38
VK_DOWN = 40
VK_F1 = 112
VK_F2 = 113
VK_F3 = 114
VK_F4 = 115
VK_F5 = 116
VK_F6 = 117
VK_F7 = 118
VK_F8 = 119
VK_F9 = 120
VK_F10 = 121
VK_F11 = 122
VK_F12 = 123
VK_PAUSE = 310

# ESC Handler Priorities per UI element
ESCPRI_OptionsMenu =    0
ESCPRI_Commands =       1   # for things like casting and issuing orders
ESCPRI_SkillTree =      2
ESCPRI_Shop =           2
ESCPRI_Character =      2

local currentLayout = nil
local currentSkin = nil

function GetNetworkBool()
    local sessionClientsTable = GetSessionClients()
    local networkBool = false
    local sessionBool = true
    if sessionClientsTable != nil then
        networkBool = SessionIsMultiplayer()
    else
        sessionBool = false
    end
    return networkBool, sessionBool
end

function GetAnimationPrefs()
    return Prefs.GetOption("ui_animation")
end

function SetLayout(layout)
    currentLayout = layout
end

function GetLayout()
    return currentLayout
end

function GetLayoutFilename(key)
    if Layouts[currentLayout] and Layouts[currentLayout][key] then
        return Layouts[currentLayout][key]
    else
        WARN('No layout file or layout key for \'', key, '\' in the current layout. Expect layout errors.')
        return false
    end
end

# skin control, sets the current skin table
function SetCurrentSkin(skin)
    local skins = import('/lua/user/skins/skins.lua').skins

    if skins[skin] == nil then
        LOG("Skin '" .. skin .. "' not found, setting to default")
        skin = 'default'
    end

    buttonFont:Set(skins[skin].buttonFont or skins['default'].buttonFont)
    buttonFontColor:Set(skins[skin].buttonFontColor or skins['default'].buttonFontColor)
    buttonFontOverColor:Set(skins[skin].buttonFontOverColor or skins['default'].buttonFontOverColor)
    buttonFontDownColor:Set(skins[skin].buttonFontDownColor or skins['default'].buttonFontDownColor)
    buttonFontSelectedColor:Set(skins[skin].buttonFontSelectedColor or skins['default'].buttonFontSelectedColor)
    dialogButtonFont:Set(skins[skin].dialogButtonFont or skins['default'].dialogButtonFont)
    bodyFont:Set(skins[skin].bodyFont or skins['default'].bodyFont)
    fixedFont:Set(skins[skin].fixedFont or skins['default'].fixedFont)
    titleFont:Set(skins[skin].titleFont or skins['default'].titleFont)
    flashFont:Set(skins[skin].flashFont or skins['default'].flashFont)
    titleFontColor:Set(skins[skin].titleFontColor or skins['default'].titleFontColor)
    fontColor:Set(skins[skin].fontColor or skins['default'].fontColor)
    specialFontColor:Set(skins[skin].specialFontColor or skins['default'].specialFontColor)
    bodyColor:Set(skins[skin].bodyColor or skins['default'].bodyColor)
    fontOverColor:Set(skins[skin].fontOverColor or skins['default'].fontOverColor)
    fontDownColor:Set(skins[skin].fontDownColor or skins['default'].fontDownColor)
    dialogCaptionColor:Set(skins[skin].dialogCaptionColor or skins['default'].dialogCaptionColor)
    dialogColumnColor:Set(skins[skin].dialogColumnColor or skins['default'].dialogColumnColor)
    dialogButtonColor:Set(skins[skin].dialogButtonColor or skins['default'].dialogButtonColor)
    highlightColor:Set(skins[skin].highlightColor or skins['default'].highlightColor)
    disabledColor:Set(skins[skin].disabledColor or skins['default'].disabledColor)
    panelColor:Set(skins[skin].panelColor or skins['default'].panelColor)
    transparentPanelColor:Set(skins[skin].transparentPanelColor or skins['default'].transparentPanelColor)
    consoleBGColor:Set(skins[skin].consoleBGColor or skins['default'].consoleBGColor)
    consoleFGColor:Set(skins[skin].consoleFGColor or skins['default'].consoleFGColor)
    consoleTextBGColor:Set(skins[skin].consoleTextBGColor or skins['default'].consoleTextBGColor)

    currentSkin = skin
end

# cycle through all available skins
function RotateSkin(direction)
    WARN('RotateSkin not supported.')
end

# cycle through all available layouts
function RotateLayout(direction)
    WARN('RotateLayout not supported.')
end

# given a path and name relative to the skin path, returns the full path based on the current skin
function UIFile(filespec)
    local skins = import('/lua/user/skins/skins.lua').skins
    local visitingSkin = currentSkin
    local currentPath = skins[visitingSkin].texturesPath

    if visitingSkin == nil or currentPath == nil then
        return nil
    end

    # if current skin is default, then don't bother trying to look for it, just append the default dir
    if visitingSkin == 'default' then
        return currentPath .. filespec
    else
        while visitingSkin do
            local curFile = currentPath .. filespec
            if DiskGetFileInfo(curFile) then
                return curFile
            else
                visitingSkin = skins[visitingSkin].default
                if visitingSkin then currentPath = skins[visitingSkin].texturesPath end
            end
        end
    end

    LOG("Warning: Unable to find file ", filespec)
    # pass out the final string anyway so resource loader can gracefully fail
    return filespec
end

# return the filename as a lazy var function to allow triggering of OnDirty
function SkinnableFile(filespec)
    return function()
        return UIFile(filespec)
    end
end

# each UI screen needs something to be responsible for parenting all its controls so
# placement and destruction can occur. This creates a group which fills the screen.
function CreateScreenGroup(root, debugName)
    if not root then return end
    local screenGroup = Group(root, debugName or "screenGroup")
    LayoutHelpers.FillParent(screenGroup, root)
    return screenGroup
end

# Get cursor information for a given cursor ID
function GetCursor(id)
    local skins = import('/lua/user/skins/skins.lua').skins
    local cursors = skins[currentSkin].cursors or skins['default'].cursors
    if not cursors[id] then
        LOG("Requested cursor not found: " .. id)
    end
    return cursors[id][1], cursors[id][2], cursors[id][3], cursors[id][4], cursors[id][5]
end

# create the one cursor used by the game
function CreateCursor()
    local cursor = Cursor(GetCursor('DEFAULT'))
    return cursor
end

# return a text object with the appropriate font set
function CreateText(parent, label, pointSize, font)
    label = LOC(label) or LOC("<LOC uiutil_0000>[no text]")
    font = font or buttonFont
    local text = Text(parent, "Text: " .. label)
    text:SetFont(font, pointSize)
    text:SetColor(fontColor)
    text:SetText(label)
    return text
end

# return a text object with the appropriate font set
function CreateLink(parent, label, pointSize, font, color, overcolor, func, underlined)
    label = LOC(label) or LOC("<LOC uiutil_0000>[no text]")
    font = font or buttonFont
    local text = LinkText(parent, "Text: " .. label)
    text:SetLinkOptions(func, color, overcolor)
    text:SetFont(font, pointSize)
    text:SetText(label)
    text:SetDropShadow(true)
    if underlined then
    	text:SetUnderlined(true)
    end
    return text
end

function SetupEditStd(control, foreColor, backColor, highlightFore, highlightBack, fontFace, fontSize, charLimit)
    if charLimit then
        control:SetMaxChars(charLimit)
    end
    if foreColor then
        control:SetForegroundColor(foreColor)
    end
    if backColor then
        control:SetBackgroundColor(backColor)
    end
    if highlightFore then
        control:SetHighlightForegroundColor(highlightFore)
    end
    if highlightBack then
        control:SetHighlightBackgroundColor(highlightBack)
    end
    if fontFace and fontSize then
        control:SetFont(fontFace, fontSize)
    end
	local oldOnCharPressed = control.OnCharPressed
    control.OnCharPressed = function(self, charcode)
        if charcode == VK_TAB then
            return true
        end
        local charLim = self:GetMaxChars()
        if STR_Utf8Len(self:GetText()) >= charLim then
            PlaySound('Forge/Interface/UI_Menu_Error_01')
        end
        return oldOnCharPressed(self, charcode)
    end
end

# return a button set up with a text overlay and a click sound
# if frontEnd is passed in, we dim the font a little for the frontend UI
function CreateButton(parent, up, down, over, disabled, selected, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue, frontEnd)
    textOffsetVert = textOffsetVert or 0
    textOffsetHorz = textOffsetHorz or 0

    local overPointSize
    if pointSize then
        overPointSize = pointSize + 2
    else
        overPointSize = 18
    end

    local btnFontColor
    if frontEnd then
        btnFontColor = 'ffc0c0c0'
    else
        btnFontColor = 'ffffffff'
    end

    if clickCue == "NO_SOUND" then
        clickCue = nil
    #else
        #clickCue = clickCue or "UI_Menu_MouseDown_Sml"
    end
    if rolloverCue == "NO_SOUND" then
        rolloverCue = nil
    #else
        #rolloverCue = rolloverCue or "UI_Menu_Rollover_Sml"
    end
    if type(up) == 'string' then
        up = SkinnableFile(up)
    end
    if type(down) == 'string' then
        down = SkinnableFile(down)
    end
    if type(over) == 'string' then
        over = SkinnableFile(over)
    end
    if type(disabled) == 'string' then
        disabled = SkinnableFile(disabled)
    end
    if type(selected) == 'string' then
        selected = SkinnableFile(selected)
    end

    local button = Button(parent, up, down, over, disabled, selected, clickCue, rolloverCue)
    button:UseAlphaHitTest(false)

    if label and pointSize then
        button.label = CreateText(button, label, pointSize)
        LayoutHelpers.AtCenterIn(button.label, button, textOffsetVert, textOffsetHorz)
        button.label:DisableHitTest()

        # if text exists, set up to grey it out
        button.OnDisable = function(self)
            Button.OnDisable(self)
            button.label:SetColor(disabledColor)
        end

        button.OnEnable = function(self)
            Button.OnEnable(self)
            button.label:SetColor(buttonFontColor)
        end
        
        button.OnSelect = function(self)
            Button.OnSelect(self)
            button.label:SetColor(buttonFontSelectedColor)
        end
        button.OnRolloverEvent = function(self, event)
            if event == 'enter' then
                button.label:SetColor(buttonFontOverColor)
                button.label:SetFont(buttonFont, overPointSize)
                #Play Sound mouseover soundeffect
                PlaySound( 'Forge/UI/snd_ui_generic_menu_over' )
            elseif event == 'exit' then
                button.label:SetColor(btnFontColor)
                button.label:SetFont(buttonFont, pointSize)
            elseif event == 'down' then
                button.label:SetColor(buttonFontDownColor)
                #Play Sound PLACEHOLDER click sound
                PlaySound( 'Forge/UI/snd_ui_generic_menu_click' )
            end
        end
    end

    return button
end

function SetNewButtonTextures(button, up, down, over, disabled, selected)
    # if strings passed in, make them skinnables, otherwise assume they are already skinnables
    if type(up) == 'string' then
        up = SkinnableFile(up)
    end
    if type(down) == 'string' then
        down = SkinnableFile(down)
    end
    if type(over) == 'string' then
        over = SkinnableFile(over)
    end
    if type(disabled) == 'string' then
        disabled = SkinnableFile(disabled)
    end
    if type(selected) == 'string' then
        selected = SkinnableFile(selected)
    end

    button:SetNewTextures(up, down, over, disabled, selected)
end

# create a button with standardized texture names
# given a path and button name prefix, generates the four button asset file names according to the naming convention
# If frontend is passed in, we dim the font color for the frontend ui buttons
function CreateButtonMovie(parent, moviepath, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue, frontend)
    
    # Create a button with dummy texture states
    local button =  CreateButton(parent
        , "/buttons/bar_light_btn_up.dds"   # up
        , "/buttons/bar_light_btn_up.dds"   # down
        , "/buttons/bar_light_btn_up.dds"   # over
        , "/buttons/bar_light_btn_up.dds"   # disabled
        , "/buttons/bar_light_btn_up.dds"   # selected
        , label
        , pointSize
        , textOffsetVert
        , textOffsetHorz
        , clickCue
        , rolloverCue
        , frontend
        )
        
    button:SetAlpha(0)
    
    local btnmovie = Movie(button, moviepath)
    btnmovie.Width:Set(function() return button.Width() + 100 end)
    btnmovie.Height:Set(function() return button.Height() + 100 end)
    LayoutHelpers.AtCenterIn(btnmovie, button)
    btnmovie.Depth:Set(function() return button.Depth() - 2 end)
    #btnmovie:DisableHitTest()  
    btnmovie:SetAlpha(0)
    btnmovie:Loop(true)

    
    button.OnRolloverEvent = function(self, event)
        if event == 'enter' then
            #button.label:SetColor(buttonFontOverColor)
            EffectHelpers.FadeIn(btnmovie, 0.3)
            btnmovie:Loop(true)
            btnmovie:Play()
            #Play Sound mouseover soundeffect
            PlaySound( 'Forge/UI/snd_ui_generic_menu_over' )
        elseif event == 'exit' then
            #button.label:SetColor(btnFontColor)
            EffectHelpers.FadeOut(btnmovie, 0.6)
            #button.movie:Stop()
        elseif event == 'down' then
            #button.label:SetColor(buttonFontDownColor)
            #Play Sound PLACEHOLDER click sound
            PlaySound( 'Forge/UI/snd_ui_generic_menu_click' )
        end
    end  
      
    return button
end

# create a button with standardized texture names
# given a path and button name prefix, generates the four button asset file names according to the naming convention
# If frontend is passed in, we dim the font color for the frontend ui buttons
function CreateButtonStd(parent, filename, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue, frontend)
    return CreateButton(parent
        , filename .. "_btn_up.dds"
        , filename .. "_btn_down.dds"
        , filename .. "_btn_over.dds"
        , filename .. "_btn_dis.dds"
        , filename .. "_btn_sel.dds"
        , label
        , pointSize
        , textOffsetVert
        , textOffsetHorz
        , 'Forge/UI/snd_ui_generic_menu_click'
        , 'Forge/UI/snd_ui_generic_menu_over'
        , frontend
        )
end

# Creates a button in which the button texture and states are separate from the icon below it
# (makes it easier for artists to create button art)
function CreateButtonStdWithIcon(parent, btnfilename, iconfilename, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue)

    # Creates a standard button using the button overlay texture
    local button = CreateButtonStd(parent, btnfilename, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue)

    # Attach an icon to it that we can modify when we disable
    # 'Disabled' icon states should be the filename + _dis.dds
    local iconpath = '/textures/ui/common' .. iconfilename .. '.dds'
    local disiconpath = '/textures/ui/common' .. iconfilename .. '_dis.dds'

    button.icon = Bitmap(button, iconpath)
    button.icon.Width:Set(function() return button.Width() - 4 end)
    button.icon.Height:Set(function() return button.Height() - 4 end)
    LayoutHelpers.AtCenterIn(button.icon, button)
    button.icon.Depth:Set(function() return button.Depth() - 1 end)
    button.icon:DisableHitTest()

    button.OnDisable = function(self)
       Button.OnDisable(self)
       self.icon:SetTexture(disiconpath)
    end

    button.OnEnable = function(self)
       Button.OnEnable(self)
       self.icon:SetTexture(iconpath)
    end

    return button
end

# Creates a dummy screen-sized button that can be clicked outside of elements (typically used to close with cancel behavior).
# OnClick behavior still needs to be defined as it can be different for each button
function CreateClickOutsideButton(parent)
    local button = CreateButton(parent
        , "/buttons/dummy.dds"
        , "/buttons/dummy.dds"
        , "/buttons/dummy.dds"
        , "/buttons/dummy.dds"
        , nil
        , nil
        , nil
        , nil
        , nil
        , nil
        , nil
        )

    LayoutHelpers.FillParent(button, GetFrame(0))
    button.Depth:Set(function() return parent.Depth() - 2 end)
    return button
end

function CreateCheckbox(parent, up, upsel, over, oversel, dis, dissel, clickCue, rollCue)
    local clickSound = clickCue or 'Forge/UI/Lobby/snd_ui_lobby_countdown'
    local rollSound = rollCue or 'Forge/UI/Lobby/snd_ui_lobby_countdown'
    local checkbox = Checkbox( parent, up, upsel, over, oversel, dis, dissel, clickSound, rollSound)
    checkbox:UseAlphaHitTest(true)
    return checkbox
end

function CreateCheckboxStd(parent, filename, clickCue, rollCue)
    local checkbox = CreateCheckbox( parent,
        SkinnableFile(filename .. '_d_btn_up.dds'),
        SkinnableFile(filename .. '_s_btn_up.dds'),
        SkinnableFile(filename .. '_d_btn_over.dds'),
        SkinnableFile(filename .. '_s_btn_over.dds'),
        SkinnableFile(filename .. '_d_btn_dis.dds'),
        SkinnableFile(filename .. '_s_btn_dis.dds'),
        clickCue, rollCue)
    return checkbox
end

function CreateDialogButtonStd(parent, filename, label, pointSize, textOffsetVert, textOffsetHorz, clickCue, rolloverCue)
    local button = CreateButtonStd(parent,filename,label,pointSize,textOffsetVert,textOffsetHorz, clickCue, rolloverCue)
    button.label:SetFont( dialogButtonFont, pointSize )
    button.label:SetColor( dialogButtonColor )
    return button
end

function SetNewButtonStdTextures(button, filename)
    SetNewButtonTextures(button
        , filename .. "_btn_up.dds"
        , filename .. "_btn_down.dds"
        , filename .. "_btn_over.dds"
        , filename .. "_btn_dis.dds"
        , filename .. "_btn_sel.dds")
end

--[[
# OLD - using old textures/file structure
function CreateVertScrollbarFor(attachto, offset, filename)
    offset = offset or 0
    local textureName = filename or '/small-vert_scroll/'
    local scrollbg = textureName..'back_scr_mid.dds'
    local scrollbarmid = textureName..'bar-mid_scr_over.dds'
    local scrollbartop = textureName..'bar-top_scr_up.dds'
    local scrollbarbot = textureName..'bar-bot_scr_up.dds'
    if filename then
        scrollbg = textureName..'back_scr_mid.dds'
        scrollbarmid = textureName..'bar-mid_scr_up.dds'
        scrollbartop = textureName..'bar-top_scr_up.dds'
        scrollbarbot = textureName..'bar-bot_scr_up.dds'
    end
    local scrollbar = Scrollbar(attachto, import('/lua/maui/scrollbar.lua').ScrollAxis.Vert)
    scrollbar:SetTextures(  UIFile(scrollbg)
                            ,UIFile(scrollbarmid)
                            ,UIFile(scrollbartop)
                            ,UIFile(scrollbarbot))

    local scrollUpButton = Button(  scrollbar
                                    , SkinnableFile(textureName..'arrow-up_scr_up.dds')
                                    , SkinnableFile(textureName..'arrow-up_scr_over.dds')
                                    , SkinnableFile(textureName..'arrow-up_scr_down.dds')
                                    , SkinnableFile(textureName..'arrow-up_scr_dis.dds')
                                    , "Forge/Interface/UI_Arrow_Click")

    local scrollDownButton = Button(  scrollbar
                                    , SkinnableFile(textureName..'arrow-down_scr_up.dds')
                                    , SkinnableFile(textureName..'arrow-down_scr_over.dds')
                                    , SkinnableFile(textureName..'arrow-down_scr_down.dds')
                                    , SkinnableFile(textureName..'arrow-down_scr_dis.dds')
                                    , "Forge/Interface/UI_Arrow_Click")

    scrollbar.Left:Set(function() return attachto.Right() + offset end)
    scrollbar.Top:Set(scrollUpButton.Bottom)
    scrollbar.Bottom:Set(scrollDownButton.Top)

    scrollUpButton.Left:Set(scrollbar.Left)
    scrollUpButton.Top:Set(attachto.Top)
    scrollDownButton.Left:Set(scrollbar.Left)
    scrollDownButton.Bottom:Set(attachto.Bottom)

    scrollbar.Right:Set(scrollUpButton.Right)

    scrollbar:AddButtons(scrollUpButton, scrollDownButton)
    scrollbar:SetScrollable(attachto)

    return scrollbar
end
]]--

# return the standard scrollbar
function CreateVertScrollbarFor(attachto, offset, filename)
    offset = offset or 0
    local textureName = filename or '/scrollbars/'
    local scrollbg = textureName..'scroll_vert_back.dds'
    local scrollbarmid = textureName..'scroll_vert_bar_mid_over.dds'
    local scrollbartop = textureName..'scroll_vert_bar_top_up.dds'
    local scrollbarbot = textureName..'scroll_vert_bar_btm_up.dds'
    if filename then
        scrollbg = textureName..'scroll_vert_back.dds'
        scrollbarmid = textureName..'scroll_vert_bar_mid_up.dds'
        scrollbartop = textureName..'scroll_vert_bar_top_up.dds'
        scrollbarbot = textureName..'scroll_vert_bar_btm_up.dds'
    end
    local scrollbar = Scrollbar(attachto, import('/lua/maui/scrollbar.lua').ScrollAxis.Vert)
    scrollbar:SetTextures(  UIFile(scrollbg)
                            ,UIFile(scrollbarmid)
                            ,UIFile(scrollbartop)
                            ,UIFile(scrollbarbot))

    local scrollUpButton = Button(  scrollbar
                                    , SkinnableFile(textureName..'scroll_vert_arrow_top_up.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_top_over.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_top_down.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_top_dis.dds')
                                    , "Forge/UI/Lobby/snd_ui_lobby_chatbox_scrollup") #Placeholder

    local scrollDownButton = Button(  scrollbar
                                    , SkinnableFile(textureName..'scroll_vert_arrow_btm_up.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_btm_over.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_btm_down.dds')
                                    , SkinnableFile(textureName..'scroll_vert_arrow_btm_dis.dds')
                                    , "Forge/UI/Lobby/snd_ui_lobby_chatbox_scrolldown")#Placeholder

    scrollbar.Left:Set(function() return attachto.Right() + offset end)
    scrollbar.Top:Set(scrollUpButton.Bottom)
    scrollbar.Bottom:Set(scrollDownButton.Top)

    scrollUpButton.Left:Set(scrollbar.Left)
    scrollUpButton.Top:Set(attachto.Top)
    scrollDownButton.Left:Set(scrollbar.Left)
    scrollDownButton.Bottom:Set(attachto.Bottom)

    scrollbar.Right:Set(scrollUpButton.Right)

    scrollbar:AddButtons(scrollUpButton, scrollDownButton)
    scrollbar:SetScrollable(attachto)

    return scrollbar
end

function CreateHorzScrollbarFor(attachto, offset)
    offset = offset or 0
    local scrollbar = Scrollbar(attachto, import('/lua/maui/scrollbar.lua').ScrollAxis.Horz)
    local scrollRightButton = Button(  scrollbar
                                    , SkinnableFile('/widgets/large-h_scr/arrow-right_scr_up.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-right_scr_down.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-right_scr_over.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-right_scr_dis.dds'))
    scrollRightButton.Right:Set(attachto.Right)
    scrollRightButton.Bottom:Set(function() return attachto.Top() + offset end)

    local scrollLeftButton = Button(  scrollbar
                                    , SkinnableFile('/widgets/large-h_scr/arrow-left_scr_up.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-left_scr_down.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-left_scr_over.dds')
                                    , SkinnableFile('/widgets/large-h_scr/arrow-left_scr_dis.dds'))
    scrollLeftButton.Left:Set(attachto.Left)
    scrollLeftButton.Bottom:Set(function() return attachto.Top() + offset end)

    scrollbar:SetTextures(  UIFile('/widgets/back_scr/back_scr_mid.dds')
                            ,UIFile('/widgets/large-h_scr/bar-mid_scr_over.dds')
                            ,UIFile('/widgets/large-h_scr/bar-right_scr_over.dds')
                            ,UIFile('/widgets/large-h_scr/bar-left_scr_over.dds'))
    scrollbar.Left:Set(scrollLeftButton.Right)
    scrollbar.Right:Set(scrollRightButton.Left)
    scrollbar.Top:Set(scrollRightButton.Top)
    scrollbar.Bottom:Set(scrollRightButton.Bottom)

    scrollbar:AddButtons(scrollLeftButton, scrollRightButton)
    scrollbar:SetScrollable(attachto)

    return scrollbar
end

# cause a dialog to get input focus, optional functions to perform when the user hits enter or escape
# functions signature is: function()
function MakeInputModal(control, onEnterFunc, onEscFunc)
    AddInputCapture(control)
    control.OnDestroy = function(self)
        RemoveInputCapture(control)
    end

    if onEnterFunc or onEscFunc then
        control.oldHandleEvent = control.HandleEvent
        control.HandleEvent = function(self, event)
            if event.Type == 'KeyDown' then
                if event.KeyCode == VK_ESCAPE then
                    if onEscFunc then
                        onEscFunc()
                    end
                elseif event.KeyCode == VK_ENTER then
                    if onEnterFunc then
                        onEnterFunc()
                    end
                end
            end
            if control.oldHandleEvent then
                return control.oldHandleEvent(self, event)
            else
                return true
            end
        end
    end
end

# create and manage an info dialog
# parent: the control to parent the dialog to
# dialogText: the text to display in the dialog
# button1Text: text for the first button (opt)
# button1Callback: callback function for the first button, signature function() (opt)
# button2Text: text for the second button (opt)
# button2Callback: callback function for the second button, signature function() (opt)
# button3Text: text for the second button (opt)
# button3Callback: callback function for the second button, signature function() (opt)
# destroyOnCallback: if true, destroy when any button is pressed (if false, you must destroy) (opt)
# modalInfo: Sets up modal info for dialog using a table in the form:
#  escapeButton = int 1-3 : the button function to mimic when the escape button is pressed
#  enterButton = int 1-3 : the button function to mimic when the enterButton is pressed
#  worldCover = bool : control if a world cover should be shown
function QuickDialog(parent, dialogText, button1Text, button1Callback, button2Text, button2Callback, button3Text, button3Callback, destroyOnCallback, modalInfo, noPause)
    # if there is a callback and destroy not specified, assume destroy
    if (destroyOnCallback == nil) and (button1Callback or button2Callback or button3Callback) then
        destroyOnCallback = true
    end
    
    if not noPause then
        local netBool, sessionBool = GetNetworkBool()
        if sessionBool then
            if not netBool then
                Pause.SetPause(true)
            end
        end
    end

    local dialog = Group(parent, "quickDialogGroup")

    LayoutHelpers.AtCenterIn(dialog, parent)
    dialog.Depth:Set(GetFrame(parent:GetRootFrame():GetTargetHead()):GetTopmostDepth() + 1)
    #local background = Bitmap(dialog, SkinnableFile('/widgets/dialog/dialog_m.dds'))
    local background = Bitmap(dialog, '/textures/ui/common/bg/bg.dds')
    CreateBorder(dialog):SetSolidColor('Gray')
    dialog._background = background
    #dialog.Width:Set(background.Width)
    #dialog.Height:Set(background.Height)
    dialog.Width:Set(460)
    dialog.Height:Set(200)
    LayoutHelpers.FillParent(background, dialog)

    dialog.textLine = {}
    dialog.textLine[1] = CreateText(dialog, "", 18, titleFont)
    #textLine[1]:SetColor(titleFontColor)
    #textLine[1].Top:Set(background.Top)
    LayoutHelpers.AtHorizontalCenterIn(dialog.textLine[1], dialog)
    LayoutHelpers.AtTopIn(dialog.textLine[1], dialog, 20)

    local textBoxWidth = (dialog.Width() - 80)
    local tempTable = import('/lua/maui/text.lua').WrapText(LOC(dialogText), textBoxWidth,
    function(text)
        return dialog.textLine[1]:GetStringAdvance(text)
    end)

    local tempLines = table.getn(tempTable)

    local prevControl = false
    for i, v in tempTable do
        if i == 1 then
            dialog.textLine[1]:SetText(v)
            prevControl = dialog.textLine[1]
        else
            dialog.textLine[i] = CreateText(dialog, v, 18, titleFont)
            #textLine[i] = SetColor(titleFontColor)
            LayoutHelpers.Below(tdialog.extLine[i], prevControl)
            LayoutHelpers.AtHorizontalCenterIn(dialog.textLine[i], dialog)
            prevControl = dialog.textLine[i]
        end
    end

    if not modalInfo or modalInfo.worldCover then
        CreateWorldCover(dialog)
    end

    local function MakeButton(text, callback)
        local button = CreateButtonStd( background, '/buttons/gen', text, 18, -2)
        button.Width:Set(70)
		button.Height:Set(40)
        if callback then
            button.OnClick = function(self)
                callback()
				if destroyOnCallback then
					dialog:Destroy()
				end
            end
        else
            button.OnClick = function(self)
                dialog:Destroy()
            end
        end
        return button
    end

    dialog._button1 = false
    dialog._button2 = false
    dialog._button3 = false

    if button1Text then
        dialog._button1 = MakeButton(button1Text, button1Callback)
        LayoutHelpers.Below(dialog._button1, background, 15)
    end
    if button2Text then
        dialog._button2 = MakeButton(button2Text, button2Callback)
        LayoutHelpers.Below(dialog._button2, background, 15)
    end
    if button3Text then
        dialog._button3 = MakeButton(button3Text, button3Callback)
        LayoutHelpers.Below(dialog._button3, background, 15)
    end

    if dialog._button3 then
        # center each button to one third of the dialog
        LayoutHelpers.AtHorizontalCenterIn(dialog._button2, dialog)
        LayoutHelpers.CenteredBelow(dialog._button2, dialog._button1, -20)
        LayoutHelpers.CenteredBelow(dialog._button3, dialog._button2, -20)
    elseif dialog._button2 then
        # center each button to half the dialog
        LayoutHelpers.AtHorizontalCenterIn(dialog._button1, dialog)
        LayoutHelpers.CenteredBelow(dialog._button1, textLine[1], 50)
        LayoutHelpers.Below(dialog._button2, dialog._button1, -20)
    elseif dialog._button1 then
        LayoutHelpers.AtHorizontalCenterIn(dialog._button1, dialog)
        LayoutHelpers.CenteredBelow(dialog._button1, dialog.textLine[1], 50)
    else
        #backgroundBottom:SetTexture(UIFile('/dialogs/dialog/panel_bmp_alt_b.dds'))
    end

    if modalInfo then
        local function OnEnterFunc()
            if modalInfo.enterButton then
                if modalInfo.enterButton == 1 then
                    if dialog._button1 then
                        dialog._button1.OnClick(dialog._button1)
                    end
                elseif modalInfo.enterButton == 2 then
                    if dialog._button2 then
                        dialog._button2.OnClick(dialog._button2)
                    end
                elseif modalInfo.enterButton == 3 then
                    if dialog._button3 then
                        dialog._button3.OnClick(dialog._button3)
                    end
                end
            end
        end

        local function OnEscFunc()
            if modalInfo.escapeButton then
                if modalInfo.escapeButton == 1 then
                    if dialog._button1 then
                        dialog._button1.OnClick(dialog._button1)
                    end
                elseif modalInfo.escapeButton == 2 then
                    if dialog._button2 then
                        dialog._button2.OnClick(dialog._button2)
                    end
                elseif modalInfo.escapeButton == 3 then
                    if dialog._button3 then
                        dialog._button3.OnClick(dialog._button3)
                    end
                end
            end
        end

        MakeInputModal(dialog, OnEnterFunc, OnEscFunc)
    end

    return dialog
end

function CreateEditStd(parent, width, height)
	local edit = Edit(parent)
	edit.Width:Set(width)
	edit.Height:Set(height)
	edit.Depth:Set(parent.Depth() + 1)
	SetupEditStd(edit, fontColor, nil, nil, nil, bodyFont, 15, 30)
	local border = CreateBorder(edit):SetSolidColor('Gray')
	border:SetAlpha(0.5)
	return edit
end

function CreateCryptEditStd(parent, width, height)
	local edit = CryptEdit(parent)
	edit.Width:Set(width)
	edit.Height:Set(height)
	edit.Depth:Set(parent.Depth() + 1)
	SetupEditStd(edit, fontColor, nil, nil, nil, bodyFont, 15, 30)
	local border = CreateBorder(edit):SetSolidColor('Gray')
	border:SetAlpha(0.5)
	return edit
end

function CreateWorldCover(parent)
    local NumFrame = GetNumRootFrames() - 1
    local worldCovers = {}
    for i = 0, NumFrame do
        local index = i
        if GetFrame(index) == parent:GetRootFrame() then
            worldCovers[index] = Bitmap(parent)
            worldCovers[index].ID = index
            worldCovers[index].OnDestroy = function(self)
                for h, x in worldCovers do
                    if x and h != self.ID then
                        x:Destroy()
                    end
                end
            end
            worldCovers[index].OnHide = function(self, hidden)
                for h, x in worldCovers do
                    if x and h != self.ID then
                        x:SetHidden(hidden)
                    end
                end
            end
        else
            worldCovers[index] = Bitmap(GetFrame(index))
        end
        worldCovers[index]:SetSolidColor('ff000000')
        LayoutHelpers.FillParent(worldCovers[index], GetFrame(index))
        worldCovers[index].Depth:Set(function() return parent.Depth() - 1 end)
        worldCovers[index]:SetAlpha(0)
        worldCovers[index]:SetNeedsFrameUpdate(true)
        worldCovers[index].OnFrame = function(self, delta)
            local targetAlpha = self:GetAlpha() + (delta * 2.5)
            if targetAlpha < .5 then
                self:SetAlpha(targetAlpha)
            else
                self:SetAlpha(.5)
                self:SetNeedsFrameUpdate(false)
            end
        end
    end

    return worldCovers
end

function ShowInfoDialog(parent, dialogText, buttonText, buttonCallback, destroyOnCallback)
    local dlg = QuickDialog(parent, dialogText, buttonText, buttonCallback, nil, nil, nil, nil, destroyOnCallback, {worldCover = true, enterButton = 1, escapeButton = 1})
    #play sound when opening dialog box
    PlaySound('Forge/UI/Lobby/snd_ui_lobby_lobby_unique_fail')
    return dlg
end

# create a table of sequential file names (useful for loading animations)
function CreateSequentialFilenameTable(root, ext, first, last, numPlaces)
    local retTable = {}
    local formatString = string.format("%%s%%0%dd.%%s", numPlaces)
    for index = first, last do
        retTable[(index - first) + 1] = SkinnableFile(string.format(formatString, root, index, ext))
    end
    return retTable
end

# create a box which is controlled by its external borders, and gives access to the "client" area as well
function CreateBox(parent)
    local border = Border(parent)
    border:SetTextures(
        SkinnableFile('/game/generic_brd/generic_brd_vert_l.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_horz_um.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ul.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ur.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ll.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_lr.dds'))
    local clientArea = Bitmap(parent, SkinnableFile('/game/generic_brd/generic_brd_m.dds'))
    border:LayoutControlInside(clientArea)
    clientArea.Width:Set(function() return clientArea.Right() - clientArea.Left() end)
    clientArea.Height:Set(function() return clientAreat.Bottom() - clientArea.Top() end)
    return border, clientArea
end

# create borders around a control, with optional background
function CreateBorder(parent, addBg)
    local border = Border(parent)
    border:SetTextures(
        SkinnableFile('/game/generic_brd/generic_brd_vert_l.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_horz_um.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ul.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ur.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_ll.dds'),
        SkinnableFile('/game/generic_brd/generic_brd_lr.dds'))
    border:LayoutAroundControl(parent)

    local bg = nil
    if addBg then
        bg = Bitmap(parent, SkinnableFile('/game/generic_brd/generic_brd_m.dds'))
        border:LayoutControlInside(bg)
        bg.Width:Set(function() return bg.Right() - bg.Left() end)
        bg.Height:Set(function() return bg.Bottom() - bg.Top() end)
    end

    return border, bg
end

function GetFactionIcon(factionIndex)
    return import('/lua/factions.lua').Factions[factionIndex + 1].Icon
end

# make sure you lay out text box before you attempt to set text
function CreateTextBox(parent)
    local box = ItemList(parent)
    box:SetFont(bodyFont, 14)
    box:SetColors(bodyColor, "black",  highlightColor, "white")
    CreateVertScrollbarFor(box)
    return box
end

function SetTextBoxText(textBox, text)
    textBox:DeleteAllItems()
    local wrapped = import('/lua/maui/text.lua').WrapText(LOC(text), textBox.Width(), function(curText) return textBox:GetStringAdvance(curText) end)
    for i, line in wrapped do
        textBox:AddItem(line)
    end
end

function GetOffsets()
    local GUI = Group(GetFrame(0))
    LayoutHelpers.FillParent(GUI, GetFrame(0))
    local screenWidth = GUI.Width()

    if screenWidth == 1024 then
       widthOffset = 0
    elseif screenWidth == 1280 then
        widthOffset = 10
    else
        #WARN('You have not created a specific offset for this resolution.')
        widthOffset = 10
    end

    return widthOffset
end

